/*
 * Copyright 2011 Clean GWT
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.cleanform.gwt.chinesechess.server.controller;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import com.cleanform.gwt.chinesechess.server.ai.AiEngine;
import com.cleanform.gwt.chinesechess.server.comet.CometManager;
import com.cleanform.gwt.chinesechess.shared.action.Actions;
import com.cleanform.gwt.chinesechess.shared.comet.UserEvent;
import com.cleanform.gwt.chinesechess.shared.comet.UserEvents;
import com.cleanform.gwt.chinesechess.shared.form.ChessForm;
import com.cleanform.gwt.chinesechess.shared.form.GameForm;
import com.cleanform.gwt.chinesechess.shared.form.GameHallMessageForm;
import com.cleanform.gwt.chinesechess.shared.form.NewGameForm;
import com.cleanform.gwt.chinesechess.shared.model.ChessGame;
import com.cleanform.gwt.chinesechess.shared.model.ChessMove;
import com.cleanform.gwt.chinesechess.shared.model.GameHelper;

/**
 * The server controller to provide game services.
 * 
 * @author Jess
 * @version 1.0
 * @since 1.0
 */
@Controller
@RequestMapping(Actions.GAME_CONTROLLER)
public class GameController {
	private Log log = LogFactory.getLog(GameController.class);

	private static final String SKEY_USER = "user";
	
	public static final int MAX_GAMES = 100;
	public static final int MAX_GAME_OBSERVERS = 5;

	private static final int GAMEID_AI_S01 = 0;
	private static final AiEngine ENGINE_AI_S01 = new AiEngine("[AI]-S01", 4);

	private CometManager cometService = CometManager.getInstance();

	private List<String> gameHallPlayers = new ArrayList<String>();

	private ChessGame[] games = new ChessGame[MAX_GAMES];

	private static List<String> PLAYERS = new ArrayList<String>();

	static {
		for (int i = 1; i <= 20; i++) {
			PLAYERS.add("Player" + i);
		}
	}

	public GameController() {
		// initial a AI-game
		games[GAMEID_AI_S01] = new ChessGame();
		games[GAMEID_AI_S01].setGameid(""+GAMEID_AI_S01);
		games[GAMEID_AI_S01].setDesc("The AI Arena");
		games[GAMEID_AI_S01].setPlayerBlack(ENGINE_AI_S01.getId());
	}

	@RequestMapping(Actions.ADMIT_DEFEAT)
	@ResponseBody
	public void admitDefeat(@RequestBody GameForm form) throws Exception {
		ChessGame game = getChessGame(form.getGameid());
		if(game == null){
			return;
		}
		synchronized (game) {
			admitDefeat(getChessGame(form.getGameid()), form.getActor());
		}
	}

	@RequestMapping(Actions.EXIT_GAME)
	@ResponseBody
	public void exitGame(@RequestBody GameForm form) throws Exception {
		ChessGame game = getChessGame(form.getGameid());
		if(game == null){
			return;
		}
		synchronized (game) {
			synchronized (gameHallPlayers) {
				GameHelper.playerExit(game, form.getActor());
				UserEvent exitEvent = new UserEvent(UserEvents.EXIT_GAME);
				exitEvent.setActor(form.getActor());
				// back to game hall
				gameHallPlayers.add(form.getActor());
				broadcast(GameHelper.allPlayers(game), exitEvent);
				if (!GameHelper.hasGamePlayer(game)) {
					UserEvent removeEvent = new UserEvent(UserEvents.REMOVE_GAME);
					removeEvent.setActor(form.getActor());
					removeEvent.setGame(GameHelper.retrieveGameIdentity(game));
					broadcast(gameHallPlayers, removeEvent);
					// remove game
					if (game.getObservers() != null) {
						for (String observer : game.getObservers()) {
							gameHallPlayers.add(observer);
						}
					}
					setChessGame(game.getGameid(), null);
					// games[game.getGameid()] = null;
				} else if (!GameHelper.checkGameReady(game)) {
					UserEvent updateEvent = new UserEvent(UserEvents.UPDATE_GAME);
					updateEvent.setActor(form.getActor());
					updateEvent.setGame(GameHelper.retrieveGameInfo(game));
					broadcast(gameHallPlayers, updateEvent);
				}
			}
		}
	}

	@RequestMapping(Actions.GAMEHALL_MSG)
	@ResponseBody
	public void gameHallMessage(@RequestBody GameHallMessageForm form) {
		UserEvent e = new UserEvent(UserEvents.GAMEHALL_MSG);
		e.setActor(form.getActor());
		e.setMessage(form.getMessage());
		broadcast(gameHallPlayers, e);
	}

	@RequestMapping(Actions.GAME_MSG)
	@ResponseBody
	public void gameMessage(@RequestBody GameForm form) {
		ChessGame game = getChessGame(form.getGameid());
		if(game == null){
			return;
		}
			synchronized (game) {
				UserEvent e = new UserEvent(UserEvents.GAME_MSG);
				e.setActor(form.getActor());
				e.setMessage(form.getMessage());
				broadcast(GameHelper.allPlayers(game), e);
			}
	}

	@RequestMapping(Actions.GET_COMET_EVENTS)
	@ResponseBody
	public List<UserEvent> getComentEvents(@RequestBody String clientid) {
		return cometService.getCometEvents(clientid);
	}

	@RequestMapping(Actions.GET_GAMES)
	@ResponseBody
	public List<ChessGame> getGames() {
		List<ChessGame> avaliables = new ArrayList<ChessGame>();
		synchronized (games) {
			for (int i = 0; i < MAX_GAMES; i++) {
				if (games[i] != null)  {
					if (GameHelper.hasGamePlayer(games[i])) {
						avaliables.add(GameHelper.retrieveGameInfo(games[i]));
					} else {
						games[i] = null;
					}
				}
			}
		}
		return avaliables;
	}

	@RequestMapping(Actions.GET_ONLINES)
	@ResponseBody
	public Collection<String> getOnlines() {
		return cometService.getClients();
	}

	@RequestMapping(Actions.JOIN_GAME)
	@ResponseBody
	public ChessGame joinGame(@RequestBody GameForm form) throws Exception {
		ChessGame game = getChessGame(form.getGameid());
		if (game == null) {
			throw new Exception("Game does not exist...");
		}
		synchronized (game) {
			synchronized (gameHallPlayers) {
				// ensure in game hall(not in game)
				if (!gameHallPlayers.contains(form.getActor())) {
					// is observer of the game
					if (!GameHelper.hasObserver(game,form.getActor())) {
						throw new Exception("You are already in a game.");
					}
				}
				if (GameHelper.playerJoin(game,form.getActor())) {
					UserEvent joinEvent = new UserEvent(UserEvents.JOIN_GAME);
					UserEvent updateEvent = new UserEvent(UserEvents.UPDATE_GAME);
					joinEvent.setActor(form.getActor());
					updateEvent.setActor(form.getActor());
					updateEvent.setGame(GameHelper.retrieveGameInfo(game));
					// exit game hall
					gameHallPlayers.remove(form.getActor());
					if (Integer.parseInt(game.getGameid()) == GAMEID_AI_S01) {
						ENGINE_AI_S01.reset();
					}
					// to update game
					broadcast(GameHelper.allPlayers(game), joinEvent);
					// to update game list
					broadcast(gameHallPlayers, updateEvent);
					return game;
				} else {
					throw new Exception("Can not join this game, please try again later!");
				}
			}
		}
	}
	
	@RequestMapping(Actions.GET_USER)
	@ResponseBody
	public String getUser(HttpServletRequest req) throws Exception {
		return (String) req.getSession().getAttribute(SKEY_USER);
	}

	@RequestMapping(Actions.LOGIN)
	@ResponseBody
	public void login(HttpServletRequest req, @RequestBody String clientid) throws Exception {
		if (req.getAttribute(SKEY_USER) != null) {
			throw new Exception(clientid + " already online!");
		}
		req.getSession().setAttribute(SKEY_USER, clientid);
		cometService.addClient(clientid);
		gameHallPlayers.add(clientid);
	}

	@RequestMapping(Actions.LOGOUT)
	@ResponseBody
	public void logout(@RequestBody String clientid) {
		cometService.removeClient(clientid);
		if (!gameHallPlayers.remove(clientid)) {
			// remove player from games

			for (ChessGame game : games) {
				if (game == null) {
					continue;
				}
				synchronized (game) {
					if (GameHelper.playerExit(game,clientid)) {
						UserEvent e = new UserEvent(UserEvents.EXIT_GAME);
						e.setActor(clientid);
						cometService.broadcast(e);
						break;
					}
				}
			}
		}
	}

	@RequestMapping(Actions.MOVE_CHESS)
	@ResponseBody
	public void moveChess(@RequestBody ChessForm form) throws Exception {
		ChessGame game = getChessGame(form.getGameid());
		ChessMove move = new ChessMove(form.getSx(), form.getSy(), form.getTx(), form.getTy(), form.getSource(),
				form.getTarget());
		if(game == null){
			return;
		}
		synchronized (game) {
			if (game.getActive() * move.getSource() < 1) {
				throw new Exception("Illegal Move!");
			}
			moveChess(game, form.getActor(), move);
			if (Integer.parseInt(game.getGameid()) == GAMEID_AI_S01) {
				if (move.checkCriticalMove()) {
					GameHelper.restart(game);
					ENGINE_AI_S01.reset();
				} else {
					// apply user move
					ENGINE_AI_S01.applyMove(move);
					// make a decision
					ChessMove aiMove = ENGINE_AI_S01.makeDecision();
					if (aiMove == null) {
						this.admitDefeat(game, ENGINE_AI_S01.getId());
					} else {
						// broadcast move event
						moveChess(game, ENGINE_AI_S01.getId(), aiMove);
						ENGINE_AI_S01.applyMove(aiMove);
						if (aiMove.checkCriticalMove()) {
							GameHelper.restart(game);
							ENGINE_AI_S01.reset();
						}
					}
				}

			} else {
				if (move.checkCriticalMove()) {
					GameHelper.restart(game);
				}
			}
		}

	}

	@RequestMapping(Actions.NEW_GAME)
	@ResponseBody
	public ChessGame newGame(@RequestBody NewGameForm form) throws Exception {
		synchronized (games) {
			synchronized (gameHallPlayers) {
				// ensure in game hall(not in game)
				if (!gameHallPlayers.contains(form.getActor())) {
					throw new Exception("You are already in a game.");
				}
				for (int i = 0; i < MAX_GAMES; i++) {
					if (games[i] == null) {
						ChessGame game = new ChessGame();
						UserEvent e = new UserEvent(UserEvents.NEW_GAME);
						game.setGameid(""+i);
						game.setDesc(i + ". " + form.getDesc());
						game.setPlayerRed(form.getActor());
						e.setActor(form.getActor());
						e.setGame(game);
						games[i] = game;
						gameHallPlayers.remove(form.getActor());
						broadcast(gameHallPlayers, e);
						return game;
					}
				}
			}
		}
		throw new Exception("Too many games!");
	}

	@RequestMapping(Actions.OBSERVE_GAME)
	@ResponseBody
	public ChessGame observeGame(@RequestBody GameForm form) throws Exception {
		ChessGame game = getChessGame(form.getGameid());
		if (game == null) {
			throw new Exception("Game does not exist...");
		}

		synchronized (game) {
			synchronized (gameHallPlayers) {
				// ensure in game hall(not in game)
				if (!gameHallPlayers.contains(form.getActor())) {
					throw new Exception("You are already in a game.");
				}
				if (GameHelper.addObserver(game,form.getActor())) {

					UserEvent e = new UserEvent(UserEvents.OBSERVE_GAME);
					e.setActor(form.getActor());
					gameHallPlayers.remove(form.getActor());
					broadcast(GameHelper.allPlayers(game), e);
					return game;
				} else {
					throw new Exception("Cant not observe this game, please try again later!");
				}
			}
		}
	}

	@RequestMapping(Actions.QUICK_LOGIN)
	@ResponseBody
	public String quickLogin(HttpServletRequest req) throws Exception {
		String clientid = null;
		for (String player : PLAYERS) {
			if (!cometService.hasClient(player)) {
				clientid = player;
				break;
			}
		}
		if (clientid == null) {
			throw new Exception("No account avalible, please try again later.");
		}
		login(req,clientid);
		return clientid;
	}

	private void admitDefeat(ChessGame game, String actor) {
		GameHelper.restart(game);
		// ChessGame gameInfo = game.getGameInfo();
		UserEvent e = new UserEvent(UserEvents.ADMIT_DEFEAT);
		// e.setGame(gameInfo);
		e.setActor(actor);
		broadcast(GameHelper.allPlayers(game), e);
		if (Integer.parseInt(game.getGameid()) == GAMEID_AI_S01) {
			ENGINE_AI_S01.reset();
		}
	}

	private void broadcast(List<String> players, UserEvent e) {
		for (String player : players) {
			// exclude actor and ai player
			if (e.getActor().equals(player) || ENGINE_AI_S01.getId().equals(player)) {
				continue;
			}
			log.info("Broadcast to client : " + player);
			cometService.addCometEvent(player, e);
		}
	}

	private ChessGame getChessGame(String gameid) {
		synchronized (games) {
			if(gameid == null){
				return null;
			}
			return games[Integer.parseInt(gameid)];
		}
	}

	private void moveChess(ChessGame game, String actor, ChessMove move) {
		UserEvent e = new UserEvent(UserEvents.MOVE_CHESS);
		GameHelper.applyMove(game, move);
		// e.setGame(game.getGameInfo());
		e.setActor(actor);
		e.setMove(move);
//		e.setSx(move.getSx());
//		e.setSy(move.getSy());
//		e.setTx(move.getTx());
//		e.setTy(move.getTy());
//		e.setSource(move.getSource());
//		e.setTarget(move.getTarget());
		broadcast(GameHelper.allPlayers(game), e);
	}

	private void setChessGame(String gameid, ChessGame chessGame) {
		synchronized (games) {
			games[Integer.parseInt(gameid)] = chessGame;
		}
	}

}
